package group.raber.saber.jdbc;

import group.raber.saber.kit.ValidateKit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.beans.TypeMismatchException;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.support.JdbcUtils;
import org.springframework.util.ClassUtils;

import java.beans.PropertyDescriptor;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * 自定义SQL字段和JavaBean的映射关系
 * Created by tisir<yangsong158@qq.com> on 2017-05-30
 */
public class BeanPropertyByMapRowMapper<T> implements RowMapper<T> {
    protected Logger logger = LoggerFactory.getLogger(getClass());

    private Map<Integer, String> fieldsMap;
    private Class<T> mappedClass;
    private Map<String, PropertyDescriptor> mappedFields;

    public BeanPropertyByMapRowMapper(Class<T> mappedClass, Map<Integer, String> fieldsMap) {
        this.mappedClass = mappedClass;
        this.fieldsMap = fieldsMap;
        initialize(mappedClass);
    }

    /**
     * Initialize the mapping metadata for the given class.
     *
     * @param mappedClass the mapped class
     */
    protected void initialize(Class<T> mappedClass) {
        this.mappedClass = mappedClass;
        this.mappedFields = new HashMap<String, PropertyDescriptor>();
        PropertyDescriptor[] pds = BeanUtils.getPropertyDescriptors(mappedClass);
        for (PropertyDescriptor pd : pds) {
            if (pd.getWriteMethod() != null) {
                this.mappedFields.put(pd.getName().toLowerCase(), pd);
            }
        }
        ValidateKit.notNull(fieldsMap, "fieldsMap Null is not allowed");
    }

    protected Object getColumnValue(ResultSet rs, int index, PropertyDescriptor pd) throws SQLException {
        return JdbcUtils.getResultSetValue(rs, index, pd.getPropertyType());
    }

    public T mapRow(ResultSet rs, final int rowNum) throws SQLException {
        ResultSetMetaData metaData = rs.getMetaData();
//        int columnCount = metaData.getColumnCount();
        T mappedObject = BeanUtils.instantiateClass(this.mappedClass);
        BeanWrapper beanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(mappedObject);

        Iterator<Integer> iterator = fieldsMap.keySet().iterator();
        while (iterator.hasNext()) {
            Integer column = iterator.next();
            String tableName = metaData.getTableName(1).toUpperCase();
            String columnName = metaData.getColumnName(column);
            String propertyName = fieldsMap.get(column);

            PropertyDescriptor pd = mappedFields.get(propertyName.toLowerCase());
            if (pd == null) {
                logger.debug("["+mappedClass.getName()+"."+propertyName+"] not exists,["+tableName+"."+columnName+"] not mapped");
                continue;
            }
            Object value = getColumnValue(rs, column, pd);

            try {
                beanWrapper.setPropertyValue(pd.getName(), value);
            } catch (TypeMismatchException ex) {
                if (value == null) {
                    logger.debug("Intercepted TypeMismatchException for row " + rowNum +
                            " and column '" + column + "' with null value when setting property '" +
                            pd.getName() + "' of type '" +
                            ClassUtils.getQualifiedName(pd.getPropertyType()) +
                            "' on object: " + mappedObject, ex);
                }
            }
        }

        return mappedObject;
    }
}
